package utils;

import core.HiddenLayer;
import core.HlSpkDistances;
import core.Phone;
import core.Speaker;

import java.io.*;
import java.util.*;

/**
 * Created with IntelliJ IDEA.
 * User: lahiru
 * Date: 5/11/14
 * Time: 10:03 PM
 * To change this template use File | Settings | File Templates.
 */
public class CalculateSpeakerStats {
    private static Map<String, List<Double>> h1Distances = new HashMap<String, List<Double>>();     // These distance maps keep the speaker distances between each speaker
    private static Map<String, List<Double>> h2Distances = new HashMap<String, List<Double>>();
    private static Map<String, List<Double>> h3Distances = new HashMap<String, List<Double>>();
    private static Map<String, List<Double>> h4Distances = new HashMap<String, List<Double>>();
    private static Map<String, List<Double>> h5Distances = new HashMap<String, List<Double>>();
    private static Map<String, List<Double>> h6Distances = new HashMap<String, List<Double>>();

    private static final double log2 = Math.log(2);
    private static int numOfSpeakers = 40;
    private static boolean euclidean = true;
    private static final int numberOfHiddenUnits = 2048;
    private static Map<Integer, HlSpkDistances> averageSpeakerDistances = new HashMap<Integer, HlSpkDistances>();
    private static final String location = "/media/Bunty/Academic/Research/speaker-effects/monophone/objects/";
    private static final String speakerVectorDirectory = "/home/lahiru/workspace/spk/hiddenLayer";
    private static boolean writeVectors = false;



    public static void main(String[] args) {
        try {
            //read the hiddenlayer1
            FileInputStream fileIn = new FileInputStream(location + "h1.ser");
            ObjectInputStream in = new ObjectInputStream(fileIn);
            HiddenLayer hiddenLayer = (HiddenLayer) in.readObject();
            accumilateSpeakerDifferences(hiddenLayer);

            //read the hiddenlayer2
            fileIn = new FileInputStream(location + "h2.ser");
            in = new ObjectInputStream(fileIn);
            hiddenLayer = (HiddenLayer) in.readObject();
            accumilateSpeakerDifferences(hiddenLayer);

            //read the hiddenlayer3
            fileIn = new FileInputStream(location + "h3.ser");
            in = new ObjectInputStream(fileIn);
            hiddenLayer = (HiddenLayer) in.readObject();
            accumilateSpeakerDifferences(hiddenLayer);

            //read the hiddenlayer4
            fileIn = new FileInputStream(location + "h4.ser");
            in = new ObjectInputStream(fileIn);
            hiddenLayer = (HiddenLayer) in.readObject();
            accumilateSpeakerDifferences(hiddenLayer);

            //read the hiddenlayer5
            fileIn = new FileInputStream(location + "h5.ser");
            in = new ObjectInputStream(fileIn);
            hiddenLayer = (HiddenLayer) in.readObject();
            accumilateSpeakerDifferences(hiddenLayer);


            //read the hiddenlayer6
            fileIn = new FileInputStream(location + "h6.ser");
            in = new ObjectInputStream(fileIn);
            hiddenLayer = (HiddenLayer) in.readObject();
            accumilateSpeakerDifferences(hiddenLayer);

            /*
            Print the average and total speaker distances
             */

            printAverageSpeakerDistances();


        } catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (ClassNotFoundException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }

    private static void printAverageSpeakerDistances() {
        for (HlSpkDistances hlSpkDistances : averageSpeakerDistances.values()) {
            System.out.println("Total Distance for Hidden layer " + hlSpkDistances.getId() + " " + hlSpkDistances.getTotalSpeakerDistance());
            System.out.println("Avg Distance for Hidden layer " + hlSpkDistances.getId() + " " + hlSpkDistances.getAverageSpeakerDistance());
        }
    }

    /*
      Needs to get an averaged vector for each speaker and  calculate the KL Divergence between them
       */
    private static void accumilateSpeakerDifferences(HiddenLayer hiddenLayer) throws IOException {

        Map<String, double[]> speakerVectors = new HashMap<String, double[]>(numOfSpeakers);

        for (Speaker speaker : hiddenLayer.getSpeakerList()) {
            int count = 0;
            double speakerVector[] = new double[numberOfHiddenUnits];
            for (Phone phone : speaker.getPhones().values()) {
                count = count + phone.getCount();
                double newVector[] = phone.getVector();
                if (newVector.length != speakerVector.length) {
                    throw new NumberFormatException();
                }

                for (int i = 0; i < speakerVector.length; i++) {
                    speakerVector[i] = 0.0;
                }

                for (int i = 0; i < speakerVector.length; i++) {
                    speakerVector[i] = speakerVector[i] + newVector[i];
                }
            }
            //average speaker vector by count
            for (int i = 0; i < speakerVector.length; i++) {
                speakerVector[i] = speakerVector[i] / count;

            }
            speakerVectors.put(speaker.getId(), speakerVector);   //todo ensure the ordering is unique always

        }


        if(writeVectors){
            writeSpeakerVectorsForWeka(hiddenLayer.getId(),speakerVectors);
        }

        List<Object> speakerList = Arrays.asList(speakerVectors.keySet().toArray());
        double totalDistance = 0.0;
        int hiddenLayerId = hiddenLayer.getId();

        for (int i = 0; i < speakerList.size(); i++) {

            List<Double> distanceVector = new ArrayList<Double>(40);  //TODO number of speakers

            String speakerID1 = (String) speakerList.get(i);
            for (int j = 0; j < speakerList.size(); j++) {
                String speakerID2 = (String) speakerList.get(j);

                //Calculate the distance between two speakers
                double distance;
                if (euclidean) {
                    distance = calculateEuclideanDistance(speakerVectors.get(speakerID1), speakerVectors.get(speakerID2));
                } else {
                    distance = calculateKLDivergenceVectors(speakerVectors.get(speakerID1), speakerVectors.get(speakerID2));
                }

                if (Double.isNaN(distance)) {
                    System.err.println("The distance is NaN for " + speakerID1 + " and " + speakerID2);
                }

                totalDistance = totalDistance + distance;
                distanceVector.add(distance);
            }
            if (hiddenLayerId == 1) {
                h1Distances.put(speakerID1, distanceVector);
            } else if (hiddenLayerId == 2) {
                h2Distances.put(speakerID1, distanceVector);
            } else if (hiddenLayerId == 3) {
                h3Distances.put(speakerID1, distanceVector);
            } else if (hiddenLayerId == 4) {
                h4Distances.put(speakerID1, distanceVector);
            } else if (hiddenLayerId == 5) {
                h5Distances.put(speakerID1, distanceVector);
            } else if (hiddenLayerId == 6) {
                h6Distances.put(speakerID1, distanceVector);
            }
        }

        calAverageSpeakerDistances(totalDistance, hiddenLayerId);
    }


    private static void writeSpeakerVectorsForWeka(int hiddenLayerID, Map<String, double[]> speakerVectors) throws IOException{
        //start writing the arff file for weka clustering
        FileWriter fileWriter = new FileWriter(speakerVectorDirectory + hiddenLayerID + ".arff");
        BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);

        bufferedWriter.write("@relation speakers");
        bufferedWriter.newLine();
        bufferedWriter.newLine();

        //write about attributes
        int numAttributes = numberOfHiddenUnits;
        for (int h = 1; h <= numAttributes; h++) {
            bufferedWriter.write("@attribute h" + h + " numeric\n");
        }
        bufferedWriter.newLine();
        bufferedWriter.newLine();

        bufferedWriter.write("@data\n");
        bufferedWriter.newLine();
        bufferedWriter.newLine();


        for(String speakerID:speakerVectors.keySet()){
            StringBuffer sb = new StringBuffer();
            double[] vector = speakerVectors.get(speakerID);
            for (int p = 0; p < numAttributes; p++) {
                if (p == numAttributes - 1) {
                    sb.append(vector[p] + "\n");
                } else {
                    sb.append(vector[p] + ",");
                }
            }
            bufferedWriter.write(sb.toString());
        }
        bufferedWriter.close();
    }

    private static void calAverageSpeakerDistances(double totalDistance, int hiddenLayerId) {
        int totalComparisons = numOfSpeakers * (numOfSpeakers - 1);
        if (hiddenLayerId == 1) {
            HlSpkDistances hlSpkDistances = new HlSpkDistances();
            hlSpkDistances.setId(hiddenLayerId);
            hlSpkDistances.setTotalSpeakerDistance(totalDistance);
            hlSpkDistances.setAverageSpeakerDistance(totalDistance / totalComparisons);
            averageSpeakerDistances.put(hiddenLayerId, hlSpkDistances);
        } else if (hiddenLayerId == 2) {
            HlSpkDistances hlSpkDistances = new HlSpkDistances();
            hlSpkDistances.setId(hiddenLayerId);
            hlSpkDistances.setTotalSpeakerDistance(totalDistance);
            hlSpkDistances.setAverageSpeakerDistance(totalDistance / totalComparisons);
            averageSpeakerDistances.put(hiddenLayerId, hlSpkDistances);
        } else if (hiddenLayerId == 3) {
            HlSpkDistances hlSpkDistances = new HlSpkDistances();
            hlSpkDistances.setId(hiddenLayerId);
            hlSpkDistances.setTotalSpeakerDistance(totalDistance);
            hlSpkDistances.setAverageSpeakerDistance(totalDistance / totalComparisons);
            averageSpeakerDistances.put(hiddenLayerId, hlSpkDistances);
        } else if (hiddenLayerId == 4) {
            HlSpkDistances hlSpkDistances = new HlSpkDistances();
            hlSpkDistances.setId(hiddenLayerId);
            hlSpkDistances.setTotalSpeakerDistance(totalDistance);
            hlSpkDistances.setAverageSpeakerDistance(totalDistance / totalComparisons);
            averageSpeakerDistances.put(hiddenLayerId, hlSpkDistances);
        } else if (hiddenLayerId == 5) {
            HlSpkDistances hlSpkDistances = new HlSpkDistances();
            hlSpkDistances.setId(hiddenLayerId);
            hlSpkDistances.setTotalSpeakerDistance(totalDistance);
            hlSpkDistances.setAverageSpeakerDistance(totalDistance / totalComparisons);
            averageSpeakerDistances.put(hiddenLayerId, hlSpkDistances);
        } else if (hiddenLayerId == 6) {
            HlSpkDistances hlSpkDistances = new HlSpkDistances();
            hlSpkDistances.setId(hiddenLayerId);
            hlSpkDistances.setTotalSpeakerDistance(totalDistance);
            hlSpkDistances.setAverageSpeakerDistance(totalDistance / totalComparisons);
            averageSpeakerDistances.put(hiddenLayerId, hlSpkDistances);
        }
    }


    public static double calculateKLDivergenceVectors(double[] vector1, double[] vector2) throws NumberFormatException {
        int length = vector1.length;
        if (length != vector2.length) {
            throw new NumberFormatException("Vector length's are not matching ");
        }

        if (vector1.length != 2048) {
            System.out.println("vector lengths are wrong ");
        }

        double kLdiv = 0.0;
        for (int i = 0; i < length; i++) {

            String vector1s = vector1[i] + "";
            String vector2s = vector2[i] + "";

            if (vector1s.equals("NaN")) {
                System.out.println("The vector1 is NaN " + i);
            }

            if (vector2s.equals("NaN")) {
                System.out.println("The vector2 is NaN " + i);
            }

            double temp = klDivergence(vector1[i], vector2[i]);
            String temps = temp + "";
            if (temps.equals("NaN")) {
//                System.out.println("The temp is NaN " + i);
//                System.out.println(vector1[i]);
//                System.out.println(vector2[i]);
                temp = 0.0; //todo change
            }


//            kLdiv = kLdiv + klDivergence(vector1[i], vector2[i]);
            kLdiv = kLdiv + temp;
//            System.out.println("temp " + temp);
        }

        return kLdiv / length;  //To change body of created methods use File | Settings | File Templates.
    }


    public static double calculateEuclideanDistance(double[] array1, double[] array2) {
        int length = array1.length;
        if (length != array2.length) {
            throw new NumberFormatException("Vector length's are not matching ");
        }

        double Sum = 0.0;
        for (int i = 0; i < array1.length; i++) {
            Sum = Sum + Math.pow((array1[i] - array2[i]), 2.0);
        }
        return Math.sqrt(Sum);
    }

    public static double klDivergence(double p1, double p2) {


        double klDiv = 0.0;

        if (p1 == 0.0 || p2 == 0.0) {
            return klDiv;
        } else {
//            return ((p1 * Math.log(p1 / p2)) + ((1 - p1) * Math.log((1 - p1) / (1 - p2)))) / log2;
            return (((p1 * Math.log(p1 / p2)) + ((1 - p1) * Math.log((1 - p1) / (1 - p2)))) + ((p2 * Math.log(p2 / p1)) + ((1 - p2) * Math.log((1 - p2) / (1 - p1))))) / log2;
        }

    }
}
